/*
Ramesh Marikhu <marikhu@yahoo.com>
Last Modified: 6th Feb 2009.
Version 0.1
*/
//#include "stdafx.h"

#include <iostream>
#include <fstream>
using namespace std;

#include "ImageManip.h"

// Static const variables (if NOT (int or enum)) initialized here 
const float ImageManip::fEpsDefaultLog = 0.1f;
const float ImageManip::fKappaDefaultHarris = 0.04f;
const float ImageManip::fThresDefaultHarris = 0.0f;
const double ImageManip::dSigma1DefaultSmooth = 0.0;
const double ImageManip::dSigma2DefaultSmooth = 0.0;

/** Constructor */
ImageManip::ImageManip()
{
	_pImageSrc = NULL;
	_pImageDst = NULL;
	_iNumRows = 0;
	_iNumCols = 0;

	_iBlockSizeHarris = iBlockSizeDefaultHarris;
	_iMaskSizeHarris = iMaskSizeDefaultHarris;
	_fKappaHarris = fKappaDefaultHarris;
	_iMaxPixelValueHarris = iMaxPixelValueDefaultHarris;
	_fThresHarris = fThresDefaultHarris;
	_iNumFeaturePtsHarris = 0;

	_iMaskSizeNMS = iMaskSizeDefaultNMS;
	
	_iConstLog = iConstDefaultLog;
	_fEpsLog = fEpsDefaultLog;

	_iThresLowCanny = iThresLowDefaultCanny;
	_iThresHighCanny = iThresHighDefaultCanny;
	_iSobelMaskSizeCanny = iSobelMaskSizeDefaultCanny ;

	_iSmoothTypeSmooth = iSmoothTypeDefaultSmooth ;
	_iSize1Smooth = iSize1DefaultSmooth;
	_iSize2Smooth = iSize2DefaultSmooth;
	_dSigma1Smooth = dSigma1DefaultSmooth;
	_dSigma2Smooth = dSigma2DefaultSmooth;

	_iEnclosureLevelHighlight = iEnclosureLevelDefaultHighlight;
}


/** Destructor */
ImageManip::~ImageManip()
{
	if( _pImageSrc != NULL )		cvReleaseImage( &_pImageSrc );
	if( _pImageDst != NULL )		cvReleaseImage( &_pImageDst );
}

/**
This function checks to see if the image is properly initalized, as required.

@param pImage The image that is to be checked
*/
void ImageManip::checkImage( IplImage *pImage ) {
    if ( !pImage ) throw Exception( "Invalid NULL image" );
    if ( pImage->nChannels != 1 ) throw Exception( "Invalid multi-channel image" );
    if ( pImage->depth != IPL_DEPTH_8U && pImage->depth != IPL_DEPTH_32F ) throw Exception( "Invalid image depth" );
}


/**
Compute Harris Feature Points for the image specified in _pImageSrc.
Apply cvCornerHarris() on the image.
Scale the image to max pixel value of 100.
Perform Non-maximal supression.

Filter out the Harris points that do not lie on Canny edges

Private variables that can be set:
	_pImageSrc
	_iBlockSizeHarris
	_iMaskSizeHarris
	_fKappaHarris

	_iMaxPixelValueHarris ( for scaleImage() )

	_iMaskSizeNMS	( for performNonMaximalSupression() )
	_iThresLowNMS
	_iThresHighNMS

@param iDepth The desired depth of the output image after scaling the Harris determinant/trace plane.
*/
void ImageManip::computeHarrisFeaturePts( int iDepth )
{
	checkImage( _pImageSrc );
	initializeImageDst( IPL_DEPTH_32F );

	cvCornerHarris( _pImageSrc, _pImageDst, _iBlockSizeHarris, _iMaskSizeHarris, _fKappaHarris );
}


/**
This function is specific to the scaling required for Harris Feature Points
In the function, _pImageSrc is an IPL_DEPTH_32F image.
The output image is _pImageDst, the depth of which depends on the input iDepth
This function scales the image values to [0 _iMaxPixelValueHarris] if iDepth = IPL_DEPTH_8U
This function scales the image values to [computed Min (maybe negative) _iMaxPixelValueHarris] if iDepth = IPL_DEPTH_32F
. The default value of _iMaxPixelValueHarris = 100. 

Private variables that can be set:
	_pImageSrc
	_iMaxPixelValueHarris

@param iDepth The required depth of the output image _pImageDst
*/
void ImageManip::scaleImage( int iDepth )
{
	if( ( iDepth != IPL_DEPTH_8U ) && ( iDepth != IPL_DEPTH_32F ) )
		throw Exception( "scaleImage(): This function requires depth of output image to be IPL_DEPTH_8U or IPL_DEPTH_32F" );

	checkImage( _pImageSrc );
	if( _pImageSrc->depth != IPL_DEPTH_32F ) throw Exception("This function requires floating-point image.");
	
	float* pImageDataHarris32F = (float *) _pImageSrc->imageData;
	int iWidthStep = _pImageSrc->widthStep/sizeof(float);

	if( iDepth == IPL_DEPTH_32F )
	{
		initializeImageDst( IPL_DEPTH_32F );
		float* pImageDstData = (float *) _pImageDst->imageData;
		
		int iRow = 0, iCol = 0;
		float fPixelVal = 0.0f;
		float fMax = 0.0f;

		// Find max pixel value
		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				fPixelVal = (float)pImageDataHarris32F[ iRow * iWidthStep + iCol ];
				if( fMax < fPixelVal )
					fMax = fPixelVal;
			}
		}
		cout << "fMax = "<< fMax << endl;
		// Scale to iMaxValue
		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				fPixelVal = (float)pImageDataHarris32F[ iRow * iWidthStep + iCol ];
				pImageDstData[ iRow * iWidthStep + iCol ] = fPixelVal * _iMaxPixelValueHarris/ fMax;
			}
		}
	}
	else if( iDepth == IPL_DEPTH_8U )
	{
		if( ( _iMaxPixelValueHarris > 255 ) || ( _iMaxPixelValueHarris < 1 ) )
			throw Exception( "scaleImage(): Need to set Max Pixel Value between 1 and 255 (inclusive)" );

		initializeImageDst( IPL_DEPTH_8U );
		unsigned char* pImageDstData = (unsigned char*) _pImageDst->imageData;
		
		int iRow = 0, iCol = 0;
		float fPixelVal = 0.0f;
		float fMax = 0.0f;

		// Find max pixel value
		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				fPixelVal = (float)pImageDataHarris32F[ iRow * iWidthStep + iCol ];
				//pImageDataHarris32F[ iRow * iWidthStep + iCol ] = fPixelVal + 0.005;
				if( fMax < fPixelVal )
					fMax = fPixelVal;
			}
		}
		cout << "fMax = "<< fMax << endl;
		// Scale to iMaxValue
		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				fPixelVal = (float)pImageDataHarris32F[ iRow * iWidthStep + iCol ];
				if( fPixelVal <= 0 ) 
					fPixelVal = 0;
				pImageDstData[ iRow * _pImageDst->widthStep + iCol ] = (int)(floor)((fPixelVal * _iMaxPixelValueHarris/ fMax) + 0.5 );
			}
		}
	}
}


/**
This function scales the values of the source image to [iMinValue, iMaxValue]
Private variables that can be set:
	_pImageSrc

@param iMinValue The minimum value of pixel in the output image
@param iMaxValue The maximum value of pixel in the output image
*/
void ImageManip::scaleImage( int iMinValue, int iMaxValue )
{
	checkImage( _pImageSrc );
	if( iMinValue == 0 && iMaxValue == 0) throw Exception(" Both Min Value and Max Value for scaling are 0");
	int iRow = 0, iCol = 0;
	float fSlope = 0.0f;
	float fIntercept = 0.0f;
	
	if( _pImageSrc->depth == IPL_DEPTH_8U )
	{
		if( iMinValue < 0 ) iMinValue = 0;
		if( iMaxValue > 255 ) iMaxValue = 255;

		int iWidthStep = _pImageSrc->widthStep;
		unsigned char* pImageData = (unsigned char*) _pImageSrc->imageData;

		initializeImageDst( IPL_DEPTH_8U );
		unsigned char* pImageDstData = (unsigned char*) _pImageDst->imageData;

		int iPixelVal = 0, iMin = 0, iMax = 0;

		// Get the min and max pixel value
		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				iPixelVal = pImageData[ iRow * iWidthStep + iCol ];
				if( iPixelVal < 0) cout << iPixelVal <<"\t";
				if( iMax < iPixelVal )
					iMax = iPixelVal;
				else if( iMin > iPixelVal )
					iMin = iPixelVal;
			}
		}

		// We have y = mx + c = T(x)
		// Here (x1, y1) = (iMin, iMinValue) and (x2, y2) = (iMax, iMaxValue)
		if( iMax == iMin ) throw Exception( "Single-valued image");

		// Enable either only min scaling or max scaling
		if( iMinValue == 0 ) iMinValue = iMin;
		if( iMaxValue == 0 ) iMaxValue = iMax;

		fSlope = (float)(iMaxValue - iMinValue) / (float)(iMax - iMin);
		fIntercept = iMinValue - fSlope * iMin;

		// Scale the image now using y = fSlope * x + fIntercept
		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				iPixelVal = pImageData[ iRow * iWidthStep + iCol ];
				pImageDstData[ iRow * iWidthStep + iCol ] = (int)(unsigned char)(floor)( (fSlope * iPixelVal + fIntercept) + 0.5 );
			}
		}
	}
	else if ( _pImageSrc->depth == IPL_DEPTH_32F )
	{
		int iWidthStep = _pImageSrc->widthStep/sizeof(float);
		float* pImageData = (float*) _pImageSrc->imageData;

		initializeImageDst( IPL_DEPTH_32F );
		float* pImageDstData = (float *) _pImageDst->imageData;

		float fPixelVal = 0, fMin = 0, fMax = 0;

		// Get the min and max pixel value
		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				fPixelVal = pImageData[ iRow * iWidthStep + iCol ];
				if( fMax < fPixelVal )
					fMax = fPixelVal;
				else if( fMin > fPixelVal )
					fMin = fPixelVal;
			}
		}

		// We have y = mx + c = T(x)
		// Here (x1, y1) = (fMin, iMinValue) and (x2, y2) = (fMax, iMaxValue)
		if( (fMax - fMin) <0.00001 ) throw Exception( "Approximately single-valued floating-point image");
		
		// Enable either only min scaling or max scaling
		if( iMinValue == 0 ) iMinValue = (int) floor(fMin+0.5);
		if( iMaxValue == 0 ) iMaxValue = (int) floor(fMax+0.5);

		fSlope = (iMaxValue - iMinValue) / (fMax - fMin);
		fIntercept = iMinValue - fSlope * fMin;

		// Scale the image now using y = fSlope * x + fIntercept
		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				fPixelVal = pImageData[ iRow * iWidthStep + iCol ];
				pImageDstData[ iRow * iWidthStep + iCol ] = (float)(floor)( (fSlope * fPixelVal + fIntercept) + 0.5 );
			}
		}
	}
}


/**
This function returns the minimum and maximum value within an image.

@param *fMinValue The minimum value in the image.
@param *fMaxValue The maximum value in the image
*/
void ImageManip::getMinMax( float *fMinValue, float *fMaxValue )
{
	checkImage( _pImageSrc );
	float fMin = 0;
	float fMax = 0;
	if( _pImageSrc->depth == IPL_DEPTH_8U )
	{
		float fPixelValue = 0;
		unsigned char *pData = (unsigned char *)_pImageSrc->imageData;
		int iWidthStep = _pImageSrc->widthStep;
		for( int iRow = 0; iRow< _pImageSrc->height; iRow++ )
		{
			for( int iCol = 0; iCol < _pImageSrc->width; iCol++ )
			{
				fPixelValue = (float)pData[ iRow * iWidthStep + iCol ];
				if( fMin > fPixelValue )
				{
					fMin = fPixelValue;
				}
				else if( fMax < fPixelValue )
				{
					fMax = fPixelValue;
				}		
			}
		}
	}
	else if( _pImageSrc->depth == IPL_DEPTH_32F )
	{
		float fPixelValue = 0;
		float *pData = (float *)_pImageSrc->imageData;
		int iWidthStep = _pImageSrc->widthStep/sizeof(float);
		for( int iRow = 0; iRow< _pImageSrc->height; iRow++ )
		{
			for( int iCol = 0; iCol < _pImageSrc->width; iCol++ )
			{
				fPixelValue = (float)pData[ iRow * iWidthStep + iCol ];
				if( fMin > fPixelValue )
				{
					fMin = fPixelValue;
				}
				else if( fMax < fPixelValue )
				{
					fMax = fPixelValue;
				}		
			}
		}
	}
	*fMinValue = fMin;
	*fMaxValue = fMax;
}


/**
This function sets the values < iThres to 0.

Private variables that can be set:
	_pImageSrc

@param iThres The value used to threshold the supression criteria.
*/
void ImageManip::supressBelowThreshold( float fThres )
{
	checkImage( _pImageSrc );
	_iNumFeaturePtsHarris = 0;
	if( _pImageSrc->depth == IPL_DEPTH_8U )
	{
		initializeImageDst( _pImageSrc->depth );
		unsigned char *pDataSrc = (unsigned char *) _pImageSrc->imageData;
		unsigned char *pDataDst = (unsigned char *)_pImageDst->imageData;
		int iWidthStep = _pImageSrc->widthStep;

		int iPixelVal = 0;
		for( int iRow = 0; iRow < _pImageSrc->height; iRow++ )
		{
			for( int iCol = 0; iCol < _pImageSrc->width; iCol++ )
			{
				iPixelVal = (int)(unsigned char)pDataSrc[ iRow * iWidthStep + iCol ];
				if( iPixelVal <= fThres )
					iPixelVal = 0;
				else
					_iNumFeaturePtsHarris++;
				pDataDst[ iRow * iWidthStep + iCol ] = iPixelVal;
			}
		}
	}
	else if( _pImageSrc->depth == IPL_DEPTH_32F )
	{
		initializeImageDst( _pImageSrc->depth );
		float *pDataSrc = (float *) _pImageSrc->imageData;
		float *pDataDst = (float *) _pImageDst->imageData;
		int iWidthStep = _pImageSrc->widthStep/sizeof(float);

		float fPixelVal = 0;
		for( int iRow = 0; iRow < _pImageSrc->height; iRow++ )
		{
			for( int iCol = 0; iCol < _pImageSrc->width; iCol++ )
			{
				fPixelVal = (float)pDataSrc[ iRow * iWidthStep + iCol ];
				if( fPixelVal <= fThres )
					fPixelVal = 0;
				else
					_iNumFeaturePtsHarris++;
				pDataDst[ iRow * iWidthStep + iCol ] = fPixelVal;
			}
		}
	}
	else
		throw Exception( "supressBelowMinAndAboveMax(): Invalid depth of source image." );
}


/**
This function is private, performs non-maximal supression. 
Using a fixed window size of ( 2 * iMaskSize + 1 )^2.

For each pixel, check iSize x iSize neighborhood whether it is has the max value.
If yes, set it to 255, else, set it to 0.

Returns IPL_DEPTH_8U depth image because output image contains values 0 or 255 only.

Private variables that can be set:
	_pImageSrc
	_iMaskSizeNMS
*/
void ImageManip::performNonMaximalSupression()
{
	checkImage( _pImageSrc );
	/** Output image constains 0 or 255, binarized, so IPL_DEPTH_8U would suffice.*/
	initializeImageDst( IPL_DEPTH_8U );
	
	bool isMaxVal = false;
	_iNumFeaturePtsHarris = 0;

	int iRow = 0, iCol = 0;
	int iMaskRow = 0, iMaskCol = 0;
	int iImageRow = 0, iImageCol = 0;
	int iWidthStep;
	int iWidthStep8U;	

	unsigned char *pcImageDstData = (unsigned char*) _pImageDst->imageData;
	iWidthStep8U = _pImageDst->widthStep;

	if( _pImageSrc->depth == IPL_DEPTH_8U )
	{
		int iPixelVal = 0, iNeighborPixelVal = 0;
		unsigned char *pcImageSrcData = (unsigned char*) _pImageSrc->imageData;
		iWidthStep = _pImageSrc->widthStep;

		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				iPixelVal = pcImageSrcData[ iRow * iWidthStep + iCol ];
				isMaxVal = true;
				if( iPixelVal <= 0 )
				{
					pcImageDstData[ iRow * iWidthStep8U + iCol ] = 0;
				}
				else
				{
					for( iMaskRow = -_iMaskSizeNMS; iMaskRow < _iMaskSizeNMS; iMaskRow++ )
					{
						for( iMaskCol = -_iMaskSizeNMS; iMaskCol < _iMaskSizeNMS; iMaskCol++)
						{
							// Obtain absolute row and column of the pixel in image 
							iImageRow = iRow + iMaskRow;
							iImageCol = iCol + iMaskCol;

							// Check image boundary
							if( iImageRow < 0 ) iImageRow = 0;
							if( iImageCol < 0 ) iImageCol = 0;
							if( iImageRow > ( _iNumRows - 1 ) ) iImageRow = _iNumRows - 1;
							if( iImageCol > ( _iNumCols - 1 ) ) iImageCol = _iNumCols - 1;
							
							if( !( iImageRow == iRow && iImageCol == iCol ) )
							{
								iNeighborPixelVal = pcImageSrcData[ iImageRow * iWidthStep + iImageCol ];
								if( iPixelVal <= iNeighborPixelVal )	 
								{
									pcImageSrcData[ iRow * iWidthStep + iCol ] = 0; 
									pcImageDstData[ iRow * iWidthStep8U + iCol ] = 0;
									isMaxVal = false;
									break;
								}
							}
						}	
						if( !isMaxVal )
							break;
					}
					if( isMaxVal ) 
					{
						pcImageDstData[ iRow * iWidthStep8U + iCol ] = 255;
						_iNumFeaturePtsHarris++;
					}
					else
					{
						pcImageDstData[ iRow * iWidthStep8U + iCol ] = 0;
					}
				}
			}
		}
	}
	else if( _pImageSrc->depth == IPL_DEPTH_32F )
	{
		float fPixelVal = 0.0f, fNeighborPixelVal = 0.0f;
		float* pfImageSrcData = (float*) _pImageSrc->imageData;
		iWidthStep = _pImageSrc->widthStep/sizeof(float);

		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				fPixelVal = pfImageSrcData[ iRow * iWidthStep + iCol ];
				isMaxVal = true;
				for( iMaskRow = -_iMaskSizeNMS; iMaskRow < _iMaskSizeNMS; iMaskRow++ )
				{
					for( iMaskCol = -_iMaskSizeNMS; iMaskCol < _iMaskSizeNMS; iMaskCol++)
					{
						// Obtain absolute row and column of the pixel in image 
						iImageRow = iRow + iMaskRow;
						iImageCol = iCol + iMaskCol;

						// Check image boundary
						if( iImageRow < 0 ) iImageRow = 0;
						if( iImageCol < 0 ) iImageCol = 0;
						if( iImageRow > ( _iNumRows - 1 ) ) iImageRow = _iNumRows - 1;
						if( iImageCol > ( _iNumCols - 1 ) ) iImageCol = _iNumCols - 1;
						
						if( !( iImageRow == iRow && iImageCol == iCol ) )
						{
							fNeighborPixelVal = pfImageSrcData[ iImageRow * iWidthStep + iImageCol ];
							if( fPixelVal <= fNeighborPixelVal )	 
							{
								pfImageSrcData[ iRow * iWidthStep + iCol ] = 0;
								pcImageDstData[ iRow * iWidthStep8U + iCol ] = 0;
								isMaxVal = false;
								break;
							}
						}
					}	
					if( !isMaxVal )
						break;
				}
				if( isMaxVal )
				{
					pcImageDstData[ iRow * iWidthStep8U + iCol ] = 255;
					_iNumFeaturePtsHarris++;
					//cout << "No. " << _iNumFeaturePtsHarris << "\t fPixelVal = " << fPixelVal << 
					//		"\t row = " << iRow << "\t col = " << iCol << endl;
				}
				else
				{
					pcImageDstData[ iRow * iWidthStep8U + iCol ] = 0;
				}
			}
		}
	} // end else if
}


/**
This function performs log transformation y = T(x) = c * log( 1 + double(x) + eps )
Private variables that can be set:
	_pImageSrc
	_iConstLog
	_iEpsLog

@param pImageSrc The source image
@param pImageDst The log transformation of the source image returned in this variable


*/
void ImageManip::computeLogTransformation()
{
	checkImage( _pImageSrc );
	initializeImageDst( IPL_DEPTH_8U );

	int iRow = 0, iCol = 0;
	int iWidthStep;

	unsigned char* pImageLogData = (unsigned char*) _pImageDst->imageData;
	int iWidthStepLog = _pImageDst->widthStep;

	if( _pImageSrc->depth == IPL_DEPTH_8U )
	{
		int iPixelVal;
		char* pImageData = _pImageSrc->imageData;
		iWidthStep = _pImageSrc->widthStep;

		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				iPixelVal = pImageData[ iRow * iWidthStep + iCol ];
				pImageLogData[ iRow * iWidthStepLog + iCol ] = (int)(unsigned char)( _iConstLog * log( 1 + double(iPixelVal) + _fEpsLog ) ); 		
			}
		}
	}	
	else if( _pImageSrc->depth == IPL_DEPTH_32F )
	{
		float fPixelVal;
		float* pImageData = (float*) _pImageSrc->imageData;
		iWidthStep = _pImageSrc->widthStep/sizeof(float);

		for( iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( iCol = 0; iCol < _iNumCols; iCol++ )
			{
				fPixelVal = (float) pImageData[ iRow * iWidthStep + iCol ];
				pImageLogData[ iRow * iWidthStepLog + iCol ] = (int)(unsigned char)( _iConstLog * log( 1 + double(fPixelVal) + _fEpsLog ) ); 		
			}
		}
	}
	else
		throw Exception("Invalid image depth. \nlog transform currently implemented for IPL_DEPTH_8U and IPL_DEPTH_32F");
}


/**
This function uses cvCanny() function to obtain the Canny Edges.

Private variables that can be set:
	_pImageSrc
	_iThresLowCanny
	_iThresHighCanny
	_iSobelMaskSizeCanny
*/
void ImageManip::getCannyEdgeImage()
{
	checkImage( _pImageSrc );
	initializeImageDst( IPL_DEPTH_8U );

	cvCanny( _pImageSrc, _pImageDst, _iThresLowCanny, _iThresHighCanny, _iSobelMaskSizeCanny );
}


/**
This function uses cvSmooth() function to obtain Smooth Image.

Private variables that can be set:
	_pImageSrc
	_iSmoothTypeSmooth
	_iSize1Smooth
	_iSize2Smooth
	_dSigma1Smooth
	_dSigma2Smooth
*/
void ImageManip::getSmoothImage()
{
	checkImage( _pImageSrc );
	initializeImageDst( IPL_DEPTH_8U );

	cvSmooth( _pImageSrc, _pImageDst, _iSmoothTypeSmooth, _iSize1Smooth, _iSize2Smooth, _dSigma1Smooth, _dSigma2Smooth );
}

/**
This function obtains the AND of two binary images [ values 0 or 255 ].
The parameter pImage has to be a binary image, which is also enforced within the
algorithm. For _pImageSrc, it is not necessary to be the binary image, but the values
are thought to be thresholded above 0. So the output image will contain the intensity values
of the input image. The pImage will be used only as a binary mask.

Private variables that can be set:
	_pImageSrc

@param pImage AND operation will be performed on _pImageSrc and pImage. 
*/
void ImageManip::getANDImage( IplImage* pImage )
{
	checkImage( _pImageSrc );
	checkImage( pImage );
	//if( _pImageSrc->depth != IPL_DEPTH_8U ) throw Exception( "GetANDImage(): requires source image to be 8-bit image" );
	//if( pImage->depth != IPL_DEPTH_8U ) throw Exception( "GetANDImage(): requires input image to be 8-bit image" );
	if( ( _pImageSrc->height != pImage->height ) || ( _pImageSrc->width!= pImage->width ) )
		throw Exception( "getANDImage(): Source images not compatible. Check image width and height." );

	initializeImageDst( _pImageSrc->depth );
	
	// Make sure the image is a binary image. [0 or 255] 
	// For generalization, and to make sure the output image has values of input image _pImageSrc
	// and that pImage parameter is like a binary mask, do not force binary on _pImageSrc
	//forceBinary( _pImageSrc );
	forceBinary( pImage );

	_iNumFeaturePtsHarris = 0;

	if( _pImageDst->depth == IPL_DEPTH_8U )
	{
		int iPixelImageSrc = 0;
		int iPixelImage = 0;
		for( int iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( int iCol = 0; iCol < _iNumCols; iCol++ )
			{
				iPixelImageSrc = (int)(unsigned char)_pImageSrc->imageData[ iRow * _pImageSrc->widthStep + iCol ];
				if( iPixelImageSrc > 0 )	
				{
					iPixelImage = (int)(unsigned char)pImage->imageData[ iRow * pImage->widthStep + iCol ];
					
					if( iPixelImage > 0 )	
					{
						_pImageDst->imageData[ iRow * _pImageDst->widthStep + iCol ] = iPixelImageSrc;
						_iNumFeaturePtsHarris++;
					}
					else
					{
						_pImageDst->imageData[ iRow * _pImageDst->widthStep + iCol ] = 0;
					}
				}
				else
				{
					_pImageDst->imageData[ iRow * _pImageDst->widthStep + iCol ] = 0;
				}
			}
		}
	}
	else if(_pImageDst->depth == IPL_DEPTH_32F )
	{
		int iWidthStep = _pImageDst->widthStep/sizeof(float); 
		float *pfDataSrc = (float *) _pImageSrc->imageData; 
		float *pfDataDst = (float *) _pImageDst->imageData; 

		float fPixelImageSrc = 0;
		int iPixelImage = 0;
		for( int iRow = 0; iRow < _iNumRows; iRow++ )
		{
			for( int iCol = 0; iCol < _iNumCols; iCol++ )
			{
				fPixelImageSrc = pfDataSrc[ iRow * iWidthStep + iCol ];
 				if( fPixelImageSrc > 0 )	
				{
					iPixelImage = (int)(unsigned char)pImage->imageData[ iRow * pImage->widthStep + iCol ];
					if( iPixelImage > 0 )	
					{
						pfDataDst[ iRow * iWidthStep + iCol ] = fPixelImageSrc;
						_iNumFeaturePtsHarris++;
					}
					else
					{
						pfDataDst[ iRow * iWidthStep + iCol ] = 0;
					}
				}
				else
				{
					pfDataDst[ iRow * iWidthStep + iCol ] = 0;
				}
			}
		}
	}
	else 
		throw Exception( "getANDImage(): Destination image has incompatible depth" );	
}


/**
This function obtains the OR of two binary images [ values 0 or 255 ]
@param pImage OR operation will be performed on _pImageSrc and pImage

Private variables that can be set:
	_pImageSrc
*/
void ImageManip::getORImage( IplImage* pImage )
{
	checkImage( _pImageSrc );
	checkImage( pImage );
	if( _pImageSrc->depth != IPL_DEPTH_8U ) throw Exception( "GetANDImage(): requires source image to be 8-bit image" );
	if( pImage->depth != IPL_DEPTH_8U ) throw Exception( "GetANDImage(): requires input image to be 8-bit image" );
	if( ( _pImageSrc->depth != IPL_DEPTH_8U ) ||
		( _pImageSrc->width != pImage->width ) ||
		( _pImageSrc->height != pImage->height ) ||
		( _pImageSrc->depth!= pImage->depth ) )
	{
		throw Exception( "getANDImage(): Source images not compatible. Check image height, width and depth" );
	}

	initializeImageDst( IPL_DEPTH_8U );
	
	// Make sure the image is a binary image. [0 or 255].
	// For generalization, and to make sure the output image has values of input image _pImageSrc
	// and that pImage parameter is like a binary mask, do not force binary on _pImageSrc
	//forceBinary( _pImageSrc );
	forceBinary( pImage );

	int iPixelImageSrc = 0, iPixelImage = 0;
	for( int iRow = 0; iRow < _iNumRows; iRow++ )
	{
		for( int iCol = 0; iCol < _iNumCols; iCol++ )
		{
			iPixelImageSrc = (int)(unsigned char)_pImageSrc->imageData[ iRow * _pImageSrc->widthStep + iCol ];
			if( iPixelImageSrc > 0 )	//if( iPixelImageSrc == 255 )
			{
				_pImageDst->imageData[ iRow * _pImageDst->widthStep + iCol ] = (unsigned char) 255;
			}
			else
			{
				iPixelImage = (int)(unsigned char)pImage->imageData[ iRow * pImage->widthStep + iCol ];
				
				if( iPixelImage > 0 )	//if( iPixelImageSrc == iPixelImage )
				{
					_pImageDst->imageData[ iRow * _pImageDst->widthStep + iCol ] = 100;
				}
				else
				{
					_pImageDst->imageData[ iRow * _pImageDst->widthStep + iCol ] = 0;
				}
			}
		}
	}
}


/**
This function converts an 8-bit image into a binary image with values 0 and 255 only
after thresholding at 128. Can use cvThreshold(...) as well.
Also, this function removes 1-pixel border values.
*/
void ImageManip::forceBinary( IplImage* pImage) {
	checkImage( pImage );
	if( pImage->depth != IPL_DEPTH_8U ) throw  Exception( "forceBinary(): This function accepts only 8-bit image" );

	int iIntensity = 0;
    for ( int iRow = 0; iRow < pImage->height; iRow++ ) {
        for ( int iCol = 0; iCol < pImage->width; iCol++ ) {
            iIntensity = (int)(unsigned char)pImage->imageData[ iRow * pImage->widthStep + iCol ];
			if( iIntensity != 0 || iIntensity != 255 )
			{
				// Eliminate any edges along image boundaries
				if ( iRow == 0 || iRow == pImage->height-1 || iCol == 0 || iCol == pImage->width-1 ) {
					iIntensity = 0;
				}
				// Force the pixel to 0 or 255
				if ( iIntensity > 128 ) {
					pImage->imageData[ iRow * pImage->widthStep + iCol ] = (unsigned char)255;
				} else {
					pImage->imageData[ iRow * pImage->widthStep + iCol ] = 0;
				}
			}
        }
    }
}

/**
This function highlights the image points onto the source image.
The image point with value 255 in the image pImage, which should be a binary image, 
is highlighted as red. This pixel is bound by a magenta square with enclosure level 2

The private functions that can be set:
	_pImageSrc
	_iEnclosureLevelHighlight

@param pImage The image that has binary points that will be highlighted on the source image
*/
IplImage* ImageManip::highlightPtsOnImage( IplImage* pImage )
{
	checkImage( _pImageSrc );
	checkImage( pImage );
	if( _pImageSrc->depth != IPL_DEPTH_8U )
		throw Exception( "highlightPtsOnImage(): This function requires the source image to be an 8-bit image" );
	
	forceBinary( pImage );
	
    IplImage *pImageHL = cvCreateImage( cvSize( _iNumCols, _iNumRows), IPL_DEPTH_8U, 3 );
	cvCvtColor( _pImageSrc, pImageHL, CV_GRAY2BGR );
    
    for ( int iRow = 0; iRow < _iNumRows; iRow++ ) 
	{
        for ( int iCol = 0; iCol < _iNumCols; iCol++ )
		{
            CvScalar color = cvScalar( 0, 0, 0, 0 );
			int iImageVal = (int)(unsigned char)pImage->imageData[ iRow * pImage->widthStep + iCol ]; 
			if ( iImageVal == 255 )
			{
				color.val[2] = 255;
	            cvSet2D( pImageHL, iRow, iCol, color );

				// Create a white square surrounding it
				for( int iMaskRow = -_iEnclosureLevelHighlight; iMaskRow <= _iEnclosureLevelHighlight; iMaskRow++ )
				{
					for( int iMaskCol = -_iEnclosureLevelHighlight; iMaskCol <= _iEnclosureLevelHighlight; iMaskCol++ )
					{
						if( ( iMaskRow < -_iEnclosureLevelHighlight/2 )  || 
							( iMaskRow >  _iEnclosureLevelHighlight/2 ) || 
							( iMaskCol < -_iEnclosureLevelHighlight/2 ) || 
							( iMaskCol >  _iEnclosureLevelHighlight/2 ) )
						{
							int iImageRow = iRow + iMaskRow; 
							int iImageCol = iCol + iMaskCol;
							if( iImageRow < 0 ) iImageRow = 0;
							if( iImageCol < 0 ) iImageCol = 0;
							if( iImageRow > _iNumRows - 1 ) iImageRow = _iNumRows - 1;
							if( iImageCol > _iNumCols - 1 ) iImageCol = _iNumCols - 1;
							color.val[0] = 255;
							cvSet2D( pImageHL, iImageRow, iImageCol, color );
						}
					}
				}
			}
        }
    }
    return pImageHL;
}


/**
This function saves the row and column values of the Harris points into a file, one pixel location per row.

Private variables that can be set
	_pImageSrc

@param *szFileName The name of the file to save the Harris points to.
*/
void ImageManip::dumpHarrisPts( const char *szFileName )
{
	checkImage( _pImageSrc );
	if( _pImageSrc->depth != IPL_DEPTH_8U )
		throw Exception( "dumpHarrisPts(): Image is not an 8-bit image." );

	ofstream outputFile;
    outputFile.open( szFileName );

	int iPixelVal = 0;
	_iNumFeaturePtsHarris = 0;
	for( int iRow = 0; iRow < _pImageSrc->height; iRow++ )
	{
		for( int iCol = 0; iCol < _pImageSrc->width; iCol++ )
		{
			iPixelVal = (int)(unsigned char)_pImageSrc->imageData[ iRow * _pImageSrc->widthStep + iCol ];
			if( iPixelVal > 0 )
			{
				_iNumFeaturePtsHarris++;
				outputFile << iRow << "\t" << iCol << endl;
			}	
		}
	}
	outputFile.close();
}


// Private Functions ===============================================================
/**
If the image is not re-initialized when the depth required is the current
depth of the image, the algorithm used for manipulation of the destination image
_pImageDst must take care of explicitly changing all the values of the image
pixels, which are not the default values when creating an image.

Also, it is found that the value of 205 is assigned to the whole image when
using cvCreateImage(...) to initialize the image.
So, all the if/else statements should tailor to changing each and every pixel in the 
destination image.
*/
void ImageManip::initializeImageDst( int iDepth )
{
	_pImageDst = NULL;
	_pImageDst = cvCreateImage( cvSize(_iNumCols, _iNumRows), iDepth, 1 );
	if( !_pImageDst )
		throw Exception( "Could not initialize destination image.");
}


// Getters and Setters ===========================================
void ImageManip::setImageSrc( IplImage* pImageSrc )
{
	checkImage( pImageSrc );
	this->_pImageSrc = NULL; 
	this->_pImageSrc = pImageSrc;
	this->_iNumRows = _pImageSrc->height;
	this->_iNumCols = _pImageSrc->width;
}

IplImage* ImageManip::getImageDst() const
{
	return _pImageDst;
}

int ImageManip::getBlockSizeHarris() const
{
	return _iBlockSizeHarris;
}

void ImageManip::setBlockSizeHarris( int iBlockSize )
{
	this->_iBlockSizeHarris = iBlockSize;
}

int ImageManip::getMaskSizeHarris() const
{
	return _iMaskSizeHarris;
}

void ImageManip::setMaskSizeHarris( int iMaskSize )
{
	this->_iMaskSizeHarris = iMaskSize;
}

float ImageManip::getKappaHarris() const
{
	return _fKappaHarris;
}

void ImageManip::setKappaHarris( float fKappa )
{
	this->_fKappaHarris = fKappa;
}

int ImageManip::getMaxPixelValueHarris() const
{
	return _iMaxPixelValueHarris;
}

void ImageManip::setMaxPixelValueHarris( int iMaxPixelValue )
{
	this->_iMaxPixelValueHarris = iMaxPixelValue;
}

int ImageManip::getNumFeaturePtsHarris() const
{
	return _iNumFeaturePtsHarris;
}

float ImageManip::getThresHarris() const
{
	return _fThresHarris; 
}

void ImageManip::setThresHarris( float fThres )
{
	this->_fThresHarris = fThres;
}

int ImageManip::getMaskSizeNMS() const
{
	return _iMaskSizeNMS;
}

void ImageManip::setMaskSizeNMS( int iMaskSize )
{
	this->_iMaskSizeNMS = iMaskSize;
}


int ImageManip::getConstLog() const
{
	return _iConstLog;
}

void ImageManip::setConstLog( int iConst )
{
	this->_iConstLog = iConst;
}

float ImageManip::getEpsLog() const
{
	return _fEpsLog;
}

void ImageManip::setEpsLog( float fEps )
{
	this->_fEpsLog = fEps;
}

int ImageManip::getThresLowCanny() const
{
	return _iThresLowCanny;
}

void ImageManip::setThresLowCanny( int iThresLow )
{
	this->_iThresLowCanny = iThresLow;
}

int ImageManip::getThresHighCanny() const
{
	return _iThresHighCanny;
}

void ImageManip::setThresHighCanny( int iThresHigh )
{
	this->_iThresHighCanny = iThresHigh;
}

int ImageManip::getSobelMaskSizeCanny() const
{
	return _iSobelMaskSizeCanny;
}

void ImageManip::setSobelMaskSizeCanny( int iSobelMaskSize )
{
	this->_iSobelMaskSizeCanny = iSobelMaskSize;
}

int ImageManip::getSmoothTypeSmooth() const
{
	return _iSmoothTypeSmooth;
}

void ImageManip::setSmoothTypeSmooth( int iSmoothType )
{
	this->_iSmoothTypeSmooth = iSmoothType;
}

int ImageManip::getSize1Smooth() const
{
	return _iSize1Smooth;
}

void ImageManip::setSize1Smooth( int iSize1 )
{
	this->_iSize1Smooth = iSize1;
}

int ImageManip::getSize2Smooth() const
{
	return _iSize2Smooth;
}

void ImageManip::setSize2Smooth( int iSize2 )
{
	this->_iSize2Smooth = iSize2;
}

double ImageManip::getSigma1Smooth() const
{
	return _dSigma1Smooth;
}

void ImageManip::setSigma1Smooth( double dSigma1 )
{
	this->_dSigma1Smooth = dSigma1;
}

double ImageManip::getSigma2Smooth() const
{
	return _dSigma2Smooth;
}

void ImageManip::setSigma2Smooth( double dSigma2 )
{
	this->_dSigma2Smooth = dSigma2;
}

int ImageManip::getEnclosureLevelHighlight() const
{
	return _iEnclosureLevelHighlight;
}

void ImageManip::setEnclosureLevelHighlight( int iEnclosureLevel )
{
	this->_iEnclosureLevelHighlight = iEnclosureLevel;
}

// Exception ===============================
ImageManip::Exception::Exception( const char *szMsg ) : std::runtime_error( szMsg ) {}
